/*
 *
 *  Copyright (C) 2010 Tal Eisenberg
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package io;

import tal.drivechain.chain.ChainNode;
import tal.drivechain.wildcard.Wildcard;
import wiiremotej.IRLight;
import wiiremotej.event.WRAccelerationEvent;
import wiiremotej.event.WRButtonEvent;
import wiiremotej.event.WRIREvent;
import wiiremotej.event.WRNunchukExtensionEvent;

/**
 *
 * @author Main
 */
public class WiiControl {    
    public static enum Control {
        MOTION_XACC(Type.MOTION, "xacc"),
        MOTION_YACC(Type.MOTION, "yacc"),
        MOTION_ZACC(Type.MOTION, "zacc"),
        MOTION_PITCH(Type.MOTION, "pitch"),
        MOTION_PITCH_DEGREES(Type.MOTION, "pitch.degrees"),
        MOTION_ROLL(Type.MOTION, "roll"),
        MOTION_ROLL_DEGREES(Type.MOTION, "roll.degrees"),
        MOTION_ACC(Type.MOTION, "acc"),
        IR_POINT1_X(Type.IR, "point1.x"),
        IR_POINT2_X(Type.IR, "point2.x"),
        IR_POINT3_X(Type.IR, "point3.x"),
        IR_POINT4_X(Type.IR, "point4.x"),
        IR_POINT1_Y(Type.IR, "point1.y"),
        IR_POINT2_Y(Type.IR, "point2.y"),
        IR_POINT3_Y(Type.IR, "point3.y"),
        IR_POINT4_Y(Type.IR, "point4.y"),
        IR_POINT1_SIZE(Type.IR, "point1.size"),
        IR_POINT2_SIZE(Type.IR, "point2.size"),
        IR_POINT3_SIZE(Type.IR, "point3.size"),
        IR_POINT4_SIZE(Type.IR, "point4.size"),
        NUNCHUK_C(Type.EXTENSION, "c"),
        NUNCHUK_Z(Type.EXTENSION, "z"),
        NUNCHUK_XACC(Type.EXTENSION, "xacc"),
        NUNCHUK_YACC(Type.EXTENSION, "yacc"),
        NUNCHUK_ZACC(Type.EXTENSION, "zacc"),
        NUNCHUK_PITCH(Type.EXTENSION, "pitch"),
        NUNCHUK_PITCH_DEGREES(Type.EXTENSION, "pitch.degrees"),
        NUNCHUK_ROLL(Type.EXTENSION, "ext.roll"),
        NUNCHUK_ROLL_DEGREES(Type.EXTENSION, "roll.degrees"),       
        NUNCHUK_ACC(Type.EXTENSION, "ext.acc"),
        NUNCHUK_ANALOG_X(Type.EXTENSION, "analog.x"),
        NUNCHUK_ANALOG_Y(Type.EXTENSION, "analog.y");
        
        public Type type;
        public String id;
        Control(Type type, String id) { this.type = type; this.id = id; }
        public static Control parseId(String i) {
            for (Control id : Control.values())
                if (id.id.equals(i.trim()))
                    return id;
            
            throw new IllegalArgumentException("Unknown Wii control id: " + i);
        }
    }
    
    public static enum Type {
        BUTTON("button"), MOTION("motion"), IR("ir"), STATUS("status"), EXTENSION("ext");
        
        public String name;
        Type(String name) { this.name = name; }

        public static Type parseType(String t) throws IllegalArgumentException {
            for (Type type : Type.values())
                if (type.name.equals(t.trim()))
                    return type;
            
            throw new IllegalArgumentException("Unknown Wii control type: " + t);
        }
    }

    public Control id;
    public int buttonId;
    public Wildcard value;
    public Type type;

    public WiiControl(int typeid, int controlid, Wildcard value) {
        type = ordinalToType(typeid);
        if (type==Type.BUTTON)
            buttonId = controlid;
        else id = ordinalToControl(controlid);
        this.value = value;
    }

    public WiiControl(Type type, Control id, Wildcard value) { this.id = id; this.value = value; this.type = type; }
    public WiiControl(int buttonId, Wildcard value) { this.buttonId = buttonId; this.type = Type.BUTTON; this.value = value; }

    public static Type ordinalToType(int ordinal) throws IllegalArgumentException {
        for (Type t : Type.values())
            if (t.ordinal()==ordinal)
                return t;

        throw new IllegalArgumentException("Unknown WiiControl type ordinal value: " + ordinal);
    }

    public static Control ordinalToControl(int ordinal) throws IllegalArgumentException {
        for (Control c : Control.values())
            if (c.ordinal()==ordinal)
                return c;

        throw new IllegalArgumentException("Unknown WiiControl control ordinal value: " + ordinal);
    }

    public static int buttonIDToInt(String controlName) throws IllegalArgumentException {
        controlName = controlName.trim().toLowerCase();
        if (controlName.equals("a")) return WRButtonEvent.A;
        else if (controlName.equals("b")) return WRButtonEvent.B;
        else if (controlName.equals("one")) return WRButtonEvent.ONE;
        else if (controlName.equals("two")) return WRButtonEvent.TWO;
        else if (controlName.equals("up")) return WRButtonEvent.UP;
        else if (controlName.equals("down")) return WRButtonEvent.DOWN;
        else if (controlName.equals("left")) return WRButtonEvent.LEFT;
        else if (controlName.equals("right")) return WRButtonEvent.RIGHT;
        else if (controlName.equals("plus")) return WRButtonEvent.PLUS;
        else if (controlName.equals("minus")) return WRButtonEvent.MINUS;
        else if (controlName.equals("home")) return WRButtonEvent.HOME;
        else throw new IllegalArgumentException("Invalid button name: " + controlName);
    }
    
    public static String buttonIntToID(int button) throws IllegalArgumentException{
        switch(button) {
            case WRButtonEvent.A: return "A";
            case WRButtonEvent.B: return "B";
            case WRButtonEvent.DOWN: return "Down";
            case WRButtonEvent.HOME: return "Home";
            case WRButtonEvent.LEFT: return "Left";
            case WRButtonEvent.MINUS: return "Minus";
            case WRButtonEvent.PLUS: return "Plus";
            case WRButtonEvent.ONE: return "One";
            case WRButtonEvent.RIGHT: return "Right";
            case WRButtonEvent.TWO: return "Two";
            case WRButtonEvent.UP: return "Up";
            default: 
                throw new IllegalArgumentException("Invalid button code " + button);
        }
    }

     public Double matches(WRButtonEvent bEvt, ChainNode context) {
        if (this.type!=Type.BUTTON) return null;
        if (bEvt.wasPressed(buttonId)) {
            if (value.matches((double)1, context)) return Double.valueOf(1);
            else return null;
        } else if (bEvt.wasReleased(buttonId)) {
            if (value.matches((double)0, context)) return Double.valueOf(0);
            else return null;
        } else return null;
     }   
     
     public Double matches(WRAccelerationEvent aEvt, ChainNode context) throws IllegalArgumentException {
        if (this.type!=Type.MOTION) return null;
        if (id==id.MOTION_PITCH) {
            double pitch = axesToPitch(aEvt.getXAcceleration(),
                    aEvt.getYAcceleration(),
                    aEvt.getZAcceleration());
            if (value.matches(pitch, context)) return Double.valueOf(pitch);
        } else if (id==id.MOTION_ROLL) {
            double roll = axesToRoll(aEvt.getXAcceleration(),
                    aEvt.getYAcceleration(),
                    aEvt.getZAcceleration());
            if (value.matches(roll, context)) return Double.valueOf(roll);
        } else if (id==id.MOTION_XACC) {
            if (value.matches(aEvt.getXAcceleration(), context)) return Double.valueOf(aEvt.getXAcceleration());
        } else if (id==id.MOTION_YACC) {
            if (value.matches(aEvt.getYAcceleration(), context)) return Double.valueOf(aEvt.getYAcceleration());
        } else if (id==id.MOTION_ZACC) {
            if (value.matches(aEvt.getZAcceleration(), context)) return Double.valueOf(aEvt.getZAcceleration());
        } else if (id==id.MOTION_ACC) {
            double combined = 
                    Math.sqrt(Math.pow(aEvt.getXAcceleration(),2)+Math.pow(aEvt.getYAcceleration(),2)+
                    (Math.pow(aEvt.getZAcceleration(),2)));
            if (value.matches(combined, context)) return combined;
        } else if (id==id.MOTION_PITCH_DEGREES) {
            double pitchdegs = radiansToDegrees(axesToPitch(aEvt.getXAcceleration(),
                    aEvt.getYAcceleration(),
                    aEvt.getZAcceleration()));
            if (value.matches(pitchdegs, context)) return Double.valueOf(pitchdegs);
        } else if (id==id.MOTION_ROLL_DEGREES) {
            double rolldegs = radiansToDegrees(axesToRoll(aEvt.getXAcceleration(),
                    aEvt.getYAcceleration(),
                    aEvt.getZAcceleration()));
            if (value.matches(rolldegs, context)) return Double.valueOf(rolldegs);
        } else throw new IllegalArgumentException("Invalid motion event id: " + id);
        return null;
     }

     public Double matches(WRIREvent irEvt, ChainNode context) throws IllegalArgumentException {
         if (this.type!=Type.IR) return null;
         IRLight[] irs = irEvt.getIRLights();
         if (id==id.IR_POINT1_X || id==id.IR_POINT1_Y || id==id.IR_POINT1_SIZE) { 
             if (irs[0]==null) return null; // point not found
             if (id==id.IR_POINT1_X) 
                if (value.matches(irs[0].getX(), context))
                    return Double.valueOf(irs[0].getX());
             else if (id==id.IR_POINT1_Y)
                 if (value.matches(irs[0].getY(), context))
                     return Double.valueOf(irs[0].getY());
             else if (id==id.IR_POINT1_SIZE) 
                 if (value.matches(irs[0].getSize(), context))
                     return Double.valueOf(irs[0].getSize());
         } else if (id==id.IR_POINT2_X || id==id.IR_POINT2_Y || id==id.IR_POINT2_SIZE) {
             if (irs[1]==null) return null; // point not found
             if (id==id.IR_POINT2_X)
                if (value.matches(irs[1].getX(), context))
                    return Double.valueOf(irs[1].getX());
             else if (id==id.IR_POINT2_Y)
                 if (value.matches(irs[1].getY(), context))
                     return Double.valueOf(irs[1].getY());
             else if (id==id.IR_POINT2_SIZE) 
                 if (value.matches(irs[1].getSize(), context))
                     return Double.valueOf(irs[1].getSize());
         } else if (id==id.IR_POINT3_X || id==id.IR_POINT3_Y || id==id.IR_POINT3_SIZE) {
             if (irs[2]==null) return null; // point not found             
             if (id==id.IR_POINT3_X)
                 if (value.matches(irs[2].getX(), context))
                     return Double.valueOf(irs[2].getX());
             else if (id==id.IR_POINT3_Y)
                 if (value.matches(irs[2].getY(), context))
                     return Double.valueOf(irs[2].getY());
             else if (id==id.IR_POINT3_SIZE) 
                 if (value.matches(irs[2].getSize(), context))
                     return Double.valueOf(irs[2].getSize());
         } else if (id==id.IR_POINT4_X || id==id.IR_POINT4_Y || id==id.IR_POINT4_SIZE) {
             if (irs[3]==null) return null; // point not found             
             if (id==id.IR_POINT4_X)
                 if (value.matches(irs[3].getX(), context))
                     return Double.valueOf(irs[3].getX());
             else if (id==id.IR_POINT4_Y)
                 if (value.matches(irs[3].getY(), context))
                     return Double.valueOf(irs[3].getY());
             else if (id==id.IR_POINT4_SIZE) 
                 if (value.matches(irs[3].getSize(), context))
                     return Double.valueOf(irs[3].getSize());
         } else throw new IllegalArgumentException("Received an invalid IR event id: " + id);
         return null;
    }
     
    public Double matches(WRNunchukExtensionEvent evt, ChainNode context) {
        if (this.type!=Type.EXTENSION) return null;
        if (id==id.NUNCHUK_C) {
            if (evt.wasPressed(WRNunchukExtensionEvent.C)) {
                if (value.matches((double)1, context)) return Double.valueOf(1);
            } else if (evt.wasReleased(WRNunchukExtensionEvent.C)) {
                if (value.matches((double)0, context)) return Double.valueOf(0);
            }
        } else if (id==id.NUNCHUK_Z) {
            if (evt.wasPressed(WRNunchukExtensionEvent.Z)) {
                if (value.matches((double)1, context)) return Double.valueOf(1);
            } else if (evt.wasReleased(WRNunchukExtensionEvent.Z)) {
                if (value.matches((double)0, context)) return Double.valueOf(0);
            }
        } else if (id==id.NUNCHUK_XACC) {
            double xacc = evt.getAcceleration().getXAcceleration();
            if (value.matches(xacc, context)) return Double.valueOf(xacc);
        } else if (id==id.NUNCHUK_YACC) {
            double yacc = evt.getAcceleration().getYAcceleration();
            if (value.matches(yacc, context)) return Double.valueOf(yacc);
        } else if (id==id.NUNCHUK_ZACC) {
            double zacc = evt.getAcceleration().getZAcceleration();
            if (value.matches(zacc, context)) return Double.valueOf(zacc);
        } else if (id==id.NUNCHUK_PITCH) {
            double pitch = axesToPitch(evt.getAcceleration().getXAcceleration(),
                    evt.getAcceleration().getYAcceleration(),
                    evt.getAcceleration().getZAcceleration());
            if (pitch!=Double.NaN || value.matches(pitch, context)) return Double.valueOf(pitch);
        } else if (id==id.NUNCHUK_ROLL) {
            double roll = axesToRoll(evt.getAcceleration().getXAcceleration(),
                    evt.getAcceleration().getYAcceleration(),
                    evt.getAcceleration().getZAcceleration());
            if (value.matches(roll, context)) return Double.valueOf(roll);
        } else if (id==id.NUNCHUK_PITCH_DEGREES) {
            double pitchdeg = radiansToDegrees(axesToPitch(evt.getAcceleration().getXAcceleration(),
                    evt.getAcceleration().getYAcceleration(),
                    evt.getAcceleration().getZAcceleration()));
            if (value.matches(pitchdeg, context)) return Double.valueOf(pitchdeg);
        } else if (id==id.NUNCHUK_ROLL_DEGREES) {
            double rolldeg = radiansToDegrees(axesToRoll(evt.getAcceleration().getXAcceleration(),
                    evt.getAcceleration().getYAcceleration(),
                    evt.getAcceleration().getZAcceleration()));
            if (value.matches(rolldeg, context)) return Double.valueOf(rolldeg);
        } else if (id==id.NUNCHUK_ANALOG_X) {
            double x = evt.getAnalogStickData().getX();
            if (value.matches(x, context)) return Double.valueOf(x);
        } else if (id==id.NUNCHUK_ANALOG_Y) {
            double y = evt.getAnalogStickData().getY();
            if (value.matches(y, context)) return Double.valueOf(y);
        } else throw new IllegalArgumentException("Invalid extension event id: " + id);
        return null;
    }
    
    public static double radiansToDegrees(double rad) {
        return rad*180/Math.PI;
    }
    
    public static double axesToPitch(double ax, double ay, double az) {
        return Math.atan2(ay, Math.sqrt(Math.pow(ax, 2) + Math.pow(az, 2)));
    }

    public static double axesToRoll(double ax, double ay, double az) {
        return Math.atan2(ax, Math.sqrt(Math.pow(ay, 2) + Math.pow(az, 2)));
    }
    
}
